package robocup.component.tactics;

import java.util.LinkedList;

import robocup.component.ControllerNumbers;
import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.actionfactories.AttentionToActionFactory;
import robocup.component.actionfactories.SayActionFactory;
import robocup.component.actionfactories.ViewModeActionFactory;
import robocup.component.actions.Action;
import robocup.component.actions.ActionSet;
import robocup.component.actions.AttentionToAction;
import robocup.component.actions.ChangeViewModeAction;
import robocup.component.actions.PointToAction;
import robocup.component.actions.SayAction;
import robocup.component.actions.TurnNeckAction;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * Abstract State Class defining the methods used in tactic evaluation.
 */
public abstract class AbstractState {

  protected final String empty = "";

  /**
   * State IDs.
   */
  public enum STATES {
    ANTI_KICK_IN,
    ATTACK,
    ATTACK_IN_LINE,
    BACKFOUR,
    BACKFOUR_POSITIONING_NEAR_MIDLINE,
    BEFORE_KICKOFF,
    CLANG,
    CLEAR_BALL,
    CORNER_POSITIONING,
    DEAD_CORNER,
    DRIBBLE,
    GOAL_KICK,
    GOALIE_CATCH_BALL,
    GOALIE_DANGER,
    GOALIE_DEFENSIVE_POSITIONING,
    GOALIE_KICK_IN,
    GOALIE_KICK_BALL,
    GOALIE_MOVE_TO_CATCH,
    GOALIE_MOVE_TO_KICK,
    GOALIE_MOVE_TO_WAIT,
    GOALIE_NO_DANGER,
    GOALIE_POSITIONING,
    GOALIE_TUNNEL,
    GOALKEEPER,
    INTERCEPT_BALL,
    INTERCEPT_OPPONENT,
    KICK_IN,
    KICK_N_RUN,
    LOOK_TO_BALL,
    MOVE_BACK,
    POSITIONING_IN_DEFENSE,
    MARK_OPPONENT_FROM_BALL,
    MARK_OPPONENT_FROM_GOAL,
    MOVE_TO_POS_WATCHING_AROUND,
    OPPONENT_INTERCEPT_BALL,
    OFFSIDE,
    PASS,
    PENALTY_GOALIE,
    POSITIONING,
    POSITION_IN_GOAL_KICK,
    RUN_FREE,
    SEARCH_BALL,
    SENSE_WORLD,
    SENSE_WORLD_WITH_BALL,
    SIMPLE_SHOOT_GOAL,
    SHOOT_GOAL,
    STOP_BALL,
    START_POSITION,
    SYNCHRONIZE_ONLY,
    TACKLE,
    TACKLE_DEAD,
    TRAINING_BEFORE_KICKOFF,
    TURN_TO_FIELD,
    WAIT,
    WAIT_WITH_BALL,
    WATCH_GOALIE_OTHER,
    DUMMY_STATE,
    OPPONENT_MOVE_BACK,
    OPPONENT_FORMATION, 
    ANTI_KICK_IN_OXSY,
    BACKPASS,
    BACKPASS_OTHER, SPELLING, 
    PENALTY_FIELD_PLAYER, 
    POSITION_PLAYER_AT_PENALTY;

    /**
     * 
     * @return true for all states where the ball is kicked
     */
    public boolean isKickState() {

      switch (this) {
        case PASS:
        case SHOOT_GOAL:
        case TACKLE:
        case CLEAR_BALL:
        case KICK_N_RUN:
        case GOAL_KICK:
          return true;
        default:
          return false;
      }
    }

    /**
     * 
     * @return true for all states where the player has the ball in the next
     *         cycle
     */
    public boolean isBallControlState() {

      switch (this) {
        case DRIBBLE:
        case WAIT_WITH_BALL:
        case GOALIE_KICK_IN:
        case GOALIE_CATCH_BALL:
        case SENSE_WORLD_WITH_BALL:
          return true;
        default:
          return false;
      }

    }
  }

  /** The current state? * */
  protected STATES               state             = STATES.DUMMY_STATE;

  /** TODO doc * */
  protected final StateEvaluation      parent;

  /**
   * These actions has to set by the state, using the corresponding methods
   * calculateXY();
   */
  protected Action               mainAction        = null;

  protected ChangeViewModeAction viewModeAction    = null;

  protected TurnNeckAction       turnNeckAction    = null;

  protected PointToAction        pointAtAction     = null;

  protected SayAction            sayAction         = null;

  protected AttentionToAction    attentionToAction = null;

  protected double               assessment        = -1.0;

  protected String               subStateString    = "";

  /**
   * Constructor. Subclasses may call super(parent,playModes);
   * 
   * @param parent
   * @param playModes
   */
  public AbstractState(
      final StateEvaluation parent) {

    this.parent = parent;
  }

  // ~ ---------------------------------------------------
  // ~ Abstract Methods, implement them in subclasses

  /**
   * This method resets the state, i.e. all previous calculated actions, values
   * and probabilities.
   */
  public void reset() {

    this.assessment = -1.0;
    this.resetActions();
  }

  public void resetActions() {

    this.mainAction = null;
    this.viewModeAction = null;
    this.turnNeckAction = null;
    this.pointAtAction = null;
    this.sayAction = null;
    this.attentionToAction = null;
  }

  public void setSubStateString() {

    this.subStateString = this.empty;
  }

  /**
   * This method gathers all preConditions and checks them in order to filter
   * all possible States
   * 
   * @return true, if this State is currently available!
   */
  public abstract boolean preCondition();

  /**
   * This method should calculate a value, that will be used to decide, how
   * "good" the application of this state is. Values may range from 0 to 1, the
   * higher the value, the more should this state be prefered. In most cases,
   * states are assessed by their success-probability and the value of the
   * resulting state of the world.
   * 
   * @return a value between 0 and 1, depending on the profit and risks of the
   *         state!
   */
  public void calculateStateAssessment() {

    this.assessment = this.successProbability() * this.successBenefit();
  }

  /**
   * simple getter.
   * 
   * @return
   */
  public double getStateAssessment() {

    if (this.assessment < 0) {
      this.calculateStateAssessment();
    }
    return this.assessment;
  }

  /**
   * These two methods are general methods for calculating the assessment of the
   * given state, depending on the current world model. Success-Probability is a
   * value from 0..1, expressing an estimate on the chance of succeeding with
   * the action defined with this state. Success-Benefit expressess the goodness
   * of the resulting worldmodel, if the action was executed successfully (zero
   * is very bad, 1 is very good)
   * 
   * @param wm -
   *          the current worldmodel
   * @return - an estimate between 0 and 1
   */
  protected abstract double successProbability();

  protected abstract double successBenefit();

  /*****************************************************************************
   * Default-Implementations for all actions are provided here: Override in
   * extending states if necessary!
   */

  /**
   * the main action has to be calculated seperatly by each state
   */
  public abstract void calculateMainAction();

  /**
   * This is the default viewmode implementation based on our synchro style
   */
  public void calculateViewModeAction() {

    this.viewModeAction = ViewModeActionFactory.getAction(this.parent.getAgent().getSynchro());
  }

  /**
   * This is the default attention to action, which should overridden in special
   * states
   */
  public void calculateAttentionToAction() {

    this.attentionToAction = AttentionToActionFactory.getAction(this.getWorld()
        .getSelfRef(), this.getState(), this.getWorld(), this.getProphet());

    // filter if not necessary
    if (attentionToAction != null && attentionToAction.getCommunicator() != null)
    if (this.attentionToAction.getCommunicator().getNumber() == this.getWorld()
        .getSelfRef().getLastHearedPlayer() && this.getWorld().getSelfRef()
        .getLastHearBodyCycle() == this.getWorld().getBodyCycle()) {
      this.attentionToAction = null;
    }
  }

  /**
   * This is the default aturn neck action, which is based upon the NeckRotator
   */
  public void calculateTurnNeckAction() {

    this.turnNeckAction = getProphet().getTurnNeckAction(this.state, this.mainAction, this.viewModeAction);
  }

  /**
   * This is the default pointTo Action implementation
   */
  @Deprecated
  public void calculatePointToAction() {

    if (this.getWorld().getSelfRef().isArmMovable() && this.getWorld()
        .getSelfRef().getStamina() < DAInamiteConstants.MAX_STAMINA_POINTED) {
      // pointAtAction = PointToActionFactory.getAction(getWorld(), mainAction);

    }
  }

  /**
   * This is the default say-action: overwrite in substates if needed!
   */
  public void calculateSayAction() {

    this.sayAction = SayActionFactory.getAction(this.getProphet(), this
        .getWorld(), this.state, this.mainAction);

  }

  // ~ ---------------------------------------------------
  // ~ useful other methods, needed by subclasses

  protected boolean checkIsKickInPlayer() {

    int kickInNumber;
    if (this.getWorld().getBallRef().getPosition().x > 0) {
      kickInNumber = 8;
    }
    else {
      kickInNumber = 6;
    }
    if (this.getWorld().getNumber() == kickInNumber) {
      return true;
    }
    return false;
  }

  /**
   * This Method returns true if the current play mode is inside the array of
   * play modes.
   * 
   * @return true, if the current playMode is contained in the playMode array;
   *         false otherwise
   */
  protected boolean checkPlayMode() {

    final LinkedList<PLAY_MODE> modes = this.getPlayer().getPlayModes(this.state);
    if (modes == null || modes.size() == 0) {
      return false;
    }

    for (final PLAY_MODE m : modes) {
      if (m.contains(this.getWorld().getPlayMode())) {
        return true;
      }
    }

    return false;
  }

  /**
   * This Method checks if the value for teamHasBall is equal to the situation
   * recognized in the worldmodel
   * 
   * @return true, if teamHasBall is equal to the situation
   */
  protected boolean checkTeamHasBall() {

    if (this.getPlayer().isFriend()) {
      // FIXME use UNCERTAIN STATE!!
      return this.getProphet().inPossessionOfBall() == ControllerNumbers.TEAM.WE;
    }
    else {
      // FIXME use UNCERTAIN STATE!!
      return this.getProphet().inPossessionOfBall() == ControllerNumbers.TEAM.OPPONENT;
    }
  }

  /**
   * This Method checks, if the value for hasBall is equal to the situation
   * recognized in the worldmodel
   * 
   * @return true, if hasBall is equal to the situation
   */
  protected boolean checkPlayerHasBall() {
    return this.parent.checkPlayerHasBall();
  }

  /**
   * @return true if the player can kick and an opponent can kick at the same
   *         time or of all teammates that can kick he has the smallest number
   *         (prevents that effect of two teammates kciking the same ball)
   */
  protected boolean checkMyBall() {
    return this.parent.checkMyBall();
  }

  /**
   * This Method checks, if the value for hasBall is equal to the situation
   * recognized in the worldmodel
   * 
   * @return true, if hasBall is equal to the situation
   */
  protected boolean checkPlayerCanTackle() {

    return this.getWorld().canTackle();
  }

  /**
   * Player is nearest to ball.
   * 
   * @return
   */
  protected boolean checkIAmFastestToBall() {

    return this.getProphet().isFastestPlayerToBall(this.getPlayer());
  }

  /**
   * Calculates if the player will be at an offside position in the next cycle.
   * 
   * @return true, if player is predicted to be at an offside position in the
   *         next cycle.
   */
  protected boolean checkOffside() {

    return this.getProphet().getNextOffsideY(true) < this.parent.getPlayer()
        .getPosition().y + Math.max(this.parent.getPlayer().getSpeed().y, 0) + 0.3;
    // return getWorld().getOffsideY(true) <
    // this.parent.getPlayer().getPosition().y +
    // Math.max(this.parent.getPlayer().getSpeed().y, 0);
  }

  /**
   * This method calculates, if the player is near a given position, where
   * "near" means, that the distance is smaller than the one observed by
   * maxDist!
   * 
   * @param pos -
   *          the considered position
   * @param maxDist -
   *          the maxDist, which is regarded as near"
   * @return true, if the player is near that pos!
   */
  protected boolean checkNearPosition(final Vektor pos,
      final double maxDist) {

    final double dist = this.getWorld().getDistance(pos);
    return (dist < maxDist);
  }

  /**
   * TODO doc
   * 
   * @param position2
   * @return
   */
  protected boolean checkDeadPositionOther(final Vektor position2) {

    return this.checkFreeKickOther();
    // return wm.getMyPosition().getDistance(position2) > 10.0 &&
    // this.checkFreeKickOther(wm);
  }

  /**
   * TODO doc
   * 
   * @return
   */
  protected boolean checkFreeKickOther() {

    final WorldModel world = this.getWorld();
    return world.getPlayMode() == PLAY_MODE.CORNER_KICK_OTHER || world
        .getPlayMode() == PLAY_MODE.FREE_KICK_OTHER || world.getPlayMode() == PLAY_MODE.GOAL_KICK_OTHER || world
        .getPlayMode() == PLAY_MODE.INDIRECT_FREE_KICK_OTHER || world
        .getPlayMode() == PLAY_MODE.KICK_IN_OTHER || world.getPlayMode() == PLAY_MODE.PENALTY_READY_OTHER || world
        .getPlayMode() == PLAY_MODE.KICK_OFF_OTHER;
  }

  /**
   * This method returns true, if checkTeamHasBall() returns true and the ball
   * is not near the own penalty area
   * 
   * @return true or false
   */
  protected boolean isModeDefensive() {

    return this.getWorld().getBallRef().getPosition().y < 30 && this
        .getProphet().inPossessionOfBall() == ControllerNumbers.TEAM.OPPONENT;
    // if (wm.getBallRef().getPosition().y < -20)
    // return true;
    // else
    // return (!checkTeamHasBall(wm));
  }

  /**
   * TODO doc ->>> GRRRRRRRR (from Gamel)
   * 
   * @param players
   * @param weightTeammates
   * @param weightOpponents
   * @return
   */
  protected Vektor calculateRejection(final Player[] players,
      final double weightTeammates,
      final double weightOpponents) {

    final WorldModel world = this.getWorld();
    double x = 0;
    double y = 0;
    double distToPlayer = 1;
    double xFromPlayer = 0;
    double yFromPlayer = 0;
    double weight;

    for (final Player p : players) {
      weight = (p.isFriend()) ? weightTeammates : weightOpponents;
      if (!p.isMe() && !p.isGoalie()) {
        distToPlayer = world.getDistance(p);
        xFromPlayer = world.getMyPosition().x - p.getPosition().x;
        yFromPlayer = world.getMyPosition().y - p.getPosition().y;
        x += xFromPlayer / (distToPlayer * distToPlayer) * weight;
        y += yFromPlayer / (distToPlayer * distToPlayer) * weight;
      }
      if (!p.isMe() && p.isGoalie()) {
        distToPlayer = world.getDistance(p);
        xFromPlayer = world.getMyPosition().x - p.getPosition().x;
        yFromPlayer = world.getMyPosition().y - p.getPosition().y;
        x += 8 * xFromPlayer / (distToPlayer * distToPlayer) * weight;
        y += 8 * yFromPlayer / (distToPlayer * distToPlayer) * weight;
      }
    }

    return new Vektor(Vektor.XY, x, y);
  }

  /**
   * Returns the global world model.
   * 
   * @return the world model of StateEvaluation
   */
  public WorldModel getWorld() {

    return this.parent.getWorld();
  }

  /**
   * Returns the global prophet.
   * 
   * @return teh world model of StateEvaluation
   */
  public Prophet getProphet() {

    return this.parent.getProphet();
  }

  /**
   * The type is a String for debugging, containing the name of the class given
   * by <b>this</b>-object
   * 
   * @return the name of the class of the current state
   */
  @Override
  public String toString() {

    if (this.state != null) {
      return this.state.toString();
    }
    else {
      return "UNKNOWN";
    }
  }

  /**
   * Returns the current state.
   * 
   * @return the current state.
   */
  public STATES getState() {

    return this.state;
  }

  /**
   * Returns the current state.
   * 
   * @return the current state.
   */
  public String getSubStateString() {

    return this.subStateString;
  }

  // Getter methods for the calculated actions

  /**
   * TODO doc
   * 
   * @return
   */
  public AttentionToAction getAttentionToAction() {

    return this.attentionToAction;
  }

  /**
   * TODO doc
   * 
   * @return
   */
  public Action getMainAction() {

    return this.mainAction;
  }

  /**
   * TODO doc
   * 
   * @return
   */
  public PointToAction getPointAtAction() {

    return this.pointAtAction;
  }

  /**
   * TODO doc
   * 
   * @return
   */
  public SayAction getSayAction() {

    return this.sayAction;
  }

  /**
   * TODO doc
   * 
   * @return
   */
  public TurnNeckAction getTurnNeckAction() {

    return this.turnNeckAction;
  }

  /**
   * TODO doc
   * 
   * @return
   */
  public ChangeViewModeAction getViewModeAction() {

    return this.viewModeAction;
  }

  /**
   * simple getter.
   * 
   * @return
   */
  public Player getPlayer() {

    return this.parent.getPlayer();
  }

  /**
   * simple getter.
   * 
   * @return
   */
  public Ball getBall() {

    return this.parent.getBall();
  }
  
  public void calculateActions(){
    calculateMainAction();
    setSubStateString();
    calculateViewModeAction();
    calculateAttentionToAction();
    calculateTurnNeckAction();
    //calculatePointToAction();
    calculateSayAction();
  }
  /**
   * @return all resulting state actions as one object.
   */
  public ActionSet getActionSet(){
    final ActionSet actions = new ActionSet();
    actions.setMain(getMainAction());
    actions.setNeck(getTurnNeckAction());
    actions.setView(getViewModeAction());
    actions.setAttention(getAttentionToAction());
    actions.setSay(getSayAction());
    actions.setPointTo(getPointAtAction());
    return actions;
  }
}